package com.knightcloud.micro.auth.security.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.knightcloud.micro.auth.api.entity.Oauth2RegisteredClient;
import com.knightcloud.micro.auth.service.IOauth2RegisteredClientService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * Oauth2客户端
 *
 * @author knight
 */
@Service
@RequiredArgsConstructor
public class MpOauth2RegisteredClientRepository implements RegisteredClientRepository, InitializingBean {

	/**
	 * oauth2注册客户服务
	 */
	private final IOauth2RegisteredClientService oauth2RegisteredClientService;

	/**
	 * jackson2对象映射器生成器
	 */
	private final Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder;

	/**
	 * 对象映射器
	 */
	private ObjectMapper objectMapper;

	@SneakyThrows
	@Override
	public void save(RegisteredClient registeredClient) {
		// 根据clientId getOne
		LambdaQueryWrapper<Oauth2RegisteredClient> queryWrapper = Wrappers.<Oauth2RegisteredClient>lambdaQuery()
			.eq(Oauth2RegisteredClient::getClientId, registeredClient.getClientId());
		Oauth2RegisteredClient db = oauth2RegisteredClientService.getOne(queryWrapper, false);
		// RegisteredClient -> Oauth2RegisteredClient
		Oauth2RegisteredClient oauth2RegisteredClient = new Oauth2RegisteredClient();
		if (Objects.nonNull(db)) {
			oauth2RegisteredClient.setId(db.getId());
		}
		oauth2RegisteredClient.setClientId(registeredClient.getClientId());
		oauth2RegisteredClient.setClientSecret(registeredClient.getClientSecret());
		oauth2RegisteredClient.setClientSecretExpiresAt(registeredClient.getClientSecretExpiresAt());
		oauth2RegisteredClient.setClientName(registeredClient.getClientName());
		oauth2RegisteredClient.setClientAuthenticationMethods(registeredClient.getClientAuthenticationMethods()
			.stream()
			.map(ClientAuthenticationMethod::getValue)
			.collect(Collectors.joining(StringPool.COMMA)));
		oauth2RegisteredClient.setAuthorizationGrantTypes(registeredClient.getAuthorizationGrantTypes()
			.stream()
			.map(AuthorizationGrantType::getValue)
			.collect(Collectors.joining(StringPool.COMMA)));
		oauth2RegisteredClient.setRedirectUris(String.join(StringPool.COMMA, registeredClient.getRedirectUris()));
		oauth2RegisteredClient.setScopes(String.join(StringPool.COMMA, registeredClient.getScopes()));
		oauth2RegisteredClient.setTokenSettings(objectMapper.writeValueAsString(registeredClient.getTokenSettings()));
		oauth2RegisteredClient.setClientSettings(objectMapper.writeValueAsString(registeredClient.getClientSettings()));
		oauth2RegisteredClientService.saveOrUpdate(oauth2RegisteredClient);
	}

	@Override
	public RegisteredClient findById(String id) {
		return convert(oauth2RegisteredClientService.getById(id));
	}

	@Override
	public RegisteredClient findByClientId(String clientId) {
		LambdaQueryWrapper<Oauth2RegisteredClient> queryWrapper = Wrappers.<Oauth2RegisteredClient>lambdaQuery()
			.eq(Oauth2RegisteredClient::getClientId, clientId);
		return convert(oauth2RegisteredClientService.getOne(queryWrapper, false));
	}

	/**
	 * 转换
	 * @param oauth2RegisteredClient oauth2注册客户端
	 * @return {@link RegisteredClient}
	 */
	@Nullable
	private RegisteredClient convert(Oauth2RegisteredClient oauth2RegisteredClient) {
		if (Objects.isNull(oauth2RegisteredClient)) {
			return null;
		}
		// token配置map
		Map<String, Object> tokenSettingMap = parseMap(oauth2RegisteredClient.getTokenSettings());
		TokenSettings tokenSettings;
		if (CollUtil.isEmpty(tokenSettingMap)) {
			// 使用默认值
			tokenSettings = TokenSettings.builder().build();
		}
		else {
			tokenSettings = TokenSettings.withSettings(tokenSettingMap).build();
		}

		// 客户端配置map
		Map<String, Object> clientSettingMap = parseMap(oauth2RegisteredClient.getClientSettings());
		ClientSettings clientSettings;
		if (CollUtil.isEmpty(clientSettingMap)) {
			// 使用默认值
			clientSettings = ClientSettings.builder().build();
		}
		else {
			clientSettings = ClientSettings.withSettings(clientSettingMap).build();
		}

		return RegisteredClient.withId(oauth2RegisteredClient.getId())
			.clientId(oauth2RegisteredClient.getClientId())
			.clientSecret(oauth2RegisteredClient.getClientSecret())
			.clientIdIssuedAt(oauth2RegisteredClient.getClientIdIssuedAt())
			.clientSecretExpiresAt(oauth2RegisteredClient.getClientSecretExpiresAt())
			.clientName(oauth2RegisteredClient.getClientName())
			.clientAuthenticationMethods(clientAuthenticationMethods -> Arrays
				.stream(oauth2RegisteredClient.getClientAuthenticationMethods().split(StringPool.COMMA))
				.map(ClientAuthenticationMethod::new)
				.forEach(clientAuthenticationMethods::add))
			.authorizationGrantTypes(authorizationGrantTypes -> Arrays
				.stream(oauth2RegisteredClient.getAuthorizationGrantTypes().split(StringPool.COMMA))
				.map(AuthorizationGrantType::new)
				.forEach(authorizationGrantTypes::add))
			.redirectUris(redirectUris -> redirectUris
				.addAll(Arrays.asList(oauth2RegisteredClient.getRedirectUris().split(StringPool.COMMA))))
			.scopes(scopes -> scopes.addAll(Arrays.stream(oauth2RegisteredClient.getScopes().split(StringPool.COMMA))
				.collect(Collectors.toSet())))
			.tokenSettings(tokenSettings)
			.clientSettings(clientSettings)
			.build();
	}

	/**
	 * 解析映射
	 * @param data 数据
	 * @return {@link Map}<{@link String}, {@link Object}>
	 */
	private Map<String, Object> parseMap(String data) {
		try {
			return this.objectMapper.readValue(data, new TypeReference<>() {
			});
		}
		catch (Exception ex) {
			throw new IllegalArgumentException(ex.getMessage(), ex);
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		objectMapper = jackson2ObjectMapperBuilder.build();
		objectMapper.registerModules(new OAuth2AuthorizationServerJackson2Module());
	}

}
